<?php
/**
*
*===================================================================
*
*  phpBB Code Repository -- Extension File
*-------------------------------------------------------------------
*	Script info:
* Version:		2.0.0 - "Xerxes" - Alpha 1
* SVN ID:		$Id: extension_livepack.php 3 2009-07-31 02:25:51Z sTraTosphere.programming $
* Copyright:	(c) 2008, 2009 | Obsidian
* License:		http://opensource.org/licenses/gpl-2.0.php | GNU Public License v2
* Package:		Includes
*
*===================================================================
*
*/

/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
    exit;
}

/**
 * Projects System - Livepack Extension class,
 * 		Live package generating extension for phpBB Code Repository 2.0.x "Xerxes" 
 * 
 * @author Obsidian
 * @copyright (c) 2008, 2009 | Obsidian
 * @license http://opensource.org/licenses/gpl-2.0.php | GNU Public License v2
 */
class projects_extension_livepack extends projects_extension_core
{

	/**
	 * Used for storing list of files that will be processed for live package generation
	 * @var array
	 */
	private static $files = array();
	
	/**
	 * Where the package generator will pull files and info from, referred to elsewhere in docs as 'source directory'.
	 * @var string
	 */
	private static $source_path = '';
	
	
	
	/**
	 * What package type we'll build as.
	 * @var string
	 */
	private static $pack_extension = '';
	
	/**
	 * What the name of the package we'll be working with is.
	 * @var string
	 */
	private static $packname = '';
	
	/**
	 * phpBB Compressed Package generation class
	 * @var object
	 */
	private static $compress = NULL;
	
	/**
	 * The time now.
	 * @var integer
	 */
	private static $time = 0;

	/**
	 * Constructor method for class projects_extension_livepack
	 */
	public function __construct()
	{
		global $config;
		self::$time = time();
		
		// Force crawldir & download extensions to be loaded beforehand.
		if(projects::registered('crawldir'))
		{
			projects::error('Extension load failure in projects_extension_livepack - prerequisite extension projects_extension_crawldir not loaded');
		}
		elseif(projects::registered('download'))
		{
			projects::error('Extension load failure in projects_extension_livepack - prerequisite extension projects_extension_download not loaded');
		}
		
		if(!$config['crs_use_livepack'])
		{
			return false;
		}
	}
	
	/**
	 * Sets up the Live Package Generator's environment.  Only call this method if we will use the generator.
	 */
	public function setup()
	{		
		self::$data['project_tag'] = projects::$project_data['project_tag'];
		self::$data['project_status'] = projects::$project_data['project_status'];
		self::$data['project_version'] = projects::$project_data['project_version'];
		self::$data['project_last_livepack'] = projects::$project_data['project_last_livepack'];
		
		// Get the project's source path in the Code Repository...
		self::$source_path 	= projects::$projects_path . '/' . self::$data['project_tag'];
		
		// What package file extension are we using?
		switch ($config['crs_package_extension'])
		{
			case PACKAGE_EXT_TAR:
				self::$pack_extension = '.tar';
			break;
			
			case PACKAGE_EXT_TAR_GZ:
				self::$pack_extension = '.tar.gz';
			break;
			
			case PACKAGE_EXT_TAR_BZ:
				self::$pack_extension = '.tar.bz';
			break;
			
			case PACKAGE_EXT_ZIP:
				self::$pack_extension = '.zip';
			break;
			
			// Unknown setting?  Default to .tar
			default:
				self::$pack_extension = '.tar';
			break;
		}
		
		// If we've got an invalid setting... *BOOM*
		if(!in_array(self::$pack_extension, compress_methods()))
		{
			projects::error('Compression method ' . substr(strrchr(self::$pack_extension, '.')) . ' is not supported by server');
		}
	}
	
	/**
	 * Runs the Live Package generator.  Will only build packages as necessary.  Packages will be deleted if they already exist.
	 * 
	 * @param mixed $update - (integer) Update ID if we're working with an update, (boolean) false if not.
	 * 
	 * @return string - Package's physical filename on server.
	 */
	public function run($update = false)
	{
		global $config;
		
		if(!$config['crs_use_livepack'])
		{
			return false;
			//trigger_error('EXTENSION_DISABLED');
		}
		
		self::$packname = projects::$packs_path . '/' . $this->build_pack_name($update);
		
		// Let's check a number of things before we actually build the package, to save the server some work.
		if(($pack_exists = $this->pack_exists($update)) == false || (!$update && !$this->check_livepack_age()))
		{
			global $db;
			// Okay, only way we can get here if the package exists is if it is too old to use.
			// So, if it exists, delete it.
			if($pack_exists)
			{
				@unlink(self::$packname);
			}
			// Okay, it looks like we're going to have to fight through building the package.
			$subdirs = $dirs = array();
			
			// Do we want to skip the cached crawldir results and use fresh every time?
			if($config['crs_livepack_skip_cache'])
			{
				// Don't use cached results!  Only use fresh.
				projects::$crawldir->filelist($dirs, self::$files, $subdirs);
			}
			else
			{
				// So what if our data's stale?  XD
				projects::$crawldir->load_filelist($dirs, self::$files, $subdirs);
			}
			// Don't need these.  :D
			unset($dirs, $subdirs);
			self::$files = $this->convert_filelist(self::$files);
			self::$build_package($update);
			
			// Set last livepack time, if it isn't an "update"
			if(!$update)
			{
				self::$data['project_last_livepack'] = self::$time;
				$sql = 'UPDATE ' . PROJECTS_TABLE . '
					SET project_last_livepack = ' . (int) self::$time . '
					WHERE project_id = ' . (int) projects::$projects_id;
				$db->sql_query($sql);
				projects::$project_data['project_last_livepack'] = self::$time;
			}
			else
			{
				// Let's set the package name for the update now...we want to be nice later on about this.
				$sql = 'UPDATE ' . PROJECTS_HISTORY_TABLE . "
				SET project_pack_name = '" . $db->sql_escape(basename(self::$packname)) . "'
				WHERE project_id = " . (int) projects::$project_id . '
					AND update_id = ' . (int) $update;
				$db->sql_query($sql);
			}
		}
		return basename(self::$packname);
	}
	
	/**
	 * Takes a package and shoves it in the user's face.
	 * 
	 * @param string $package_name - The filename of the package
	 * @param mixed $update - (integer) Update ID if we're working with an update, (boolean) false if not.
	 */
	public function download_package($package_name, $update = false)
	{
		global $db;
		$package_name = projects::$packs_path . '/' . $package_name;
		// Update download count
		$sql = 'UPDATE ' . PROJECTS_TABLE . '
			SET project_downloads = project_downloads + 1
			WHERE project_id = ' . (int) projects::$project_id;
		$db->sql_query($sql);
		if($update !== false)
		{
			// Update the download count for this project history entry as well.  ;)
			$sql = 'UPDATE ' . PROJECTS_HISTORY_TABLE . '
				SET project_downloads = project_downloads + 1
				WHERE project_id = ' . (int) projects::$project_id . '
					AND update_id = ' . (int) $update;
			$db->sql_query($sql);
		}
		$package = array(
			'physical_filename'	=> basename($package_name),
			'real_filename'		=> basename($package_name),
			'mimetype'			=> get_mimetype($package_name),
			'filetime'			=> filemtime($package_name),
		);
		projects::$download->push_file($package, projects::$packs_path, ATTACHMENT_CATEGORY_NONE);
	}
	
	/**
	 * Create a package file. This function should NOT be called directly, only called through its framework.
	 * 
	 * @param mixed $update - (integer) Update ID if we're working with an update, (boolean) false if not.
	 */
	private function build_package($update)
	{
		if (self::$pack_extension == '.zip')
		{
			self::$compress = new compress_zip('w', self::$packname);
		}
		else
		{
			self::$compress = new compress_tar('w', self::$packname, self::$pack_extension);
		}
				
		// Correct path locations before adding files
		$search = array(self::$source_path);
		$replace = array('');
		
		// Add all the files!
		$file_listing = array_unique(self::$files);
		foreach ($file_listing as $file)
		{
			$file = self::$source_path . $file;
			/**
			 * If we can't read the file or it doesn't exist, don't pack it up.
			 * @NOTE: This is because we may be dealing with projects_extension_crawldir::load_filelist() and not
			 * projects_extension_crawldir::crawldir(), so the data we have may not be accurate.
			 */
			if(!@file_exists($file) || !@is_readable($file))
			{
				continue;
			}
			self::$compress->add_custom_file($file, str_replace($search, $replace, $file));
		}
		unset($file_listing);
		
		// Close package file
		self::$compress->close();
	}
	
	/**
	 * Converts the file listing generated by crawldir into one that phpBB's Compressed Package generator expects.
	 * 
	 * @param array $filelist - Filelist in crawldir's output format
	 * 
	 * @return array - Filelist in the phpBB Compressed Package generator's format.
	 */
	private function convert_filelist($filelist)
	{
		$return = array();
		foreach($filelist as $subarray)
		{
			$return = array_merge($return, $subarray);
		}
		return $return;
	}
	
	/**
	 * Checks to see if the live package file is too outdated to reuse
	 * 
	 * @return boolean - Whether the live package should be reused.
	 */
	private function check_livepack_age()
	{
		global $config;
		$max_age = 86400 * (int) $config['crs_livepack_max_age'];
		if((self::$time - $max_age) > self::$data['project_last_livepack'])
		{
			return true; // It's okay to use, it's recent.
		}
		else
		{
			return false; // OHNOZ, IT'S OLD!  D:
		}
	}

	/**
	 * Checks to see if a package with the specified name exists
	 * 
	 * @param mixed $update - (integer) Update ID if we're working with an update, (boolean) false if not.
	 * 
	 * @return boolean - Doth the package exist?
	 */
	private function pack_exists($update = false)
	{
		if(@file_exists(self::$packname) && @is_readable(self::$packname) && @is_file(self::$packname))
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	
	/**
	 * Builds a package name
	 * 
	 * @param mixed $update - (integer) Update ID if we're working with an update, (boolean) false if not.
	 * 
	 * @return string - What the package name is, with extension and all.  :P 
	 */
	private function build_pack_name($update = false)
	{
		if(!$update)
		{
			$name = 'live_' . self::$data['project_tag'];
			$name .= '_v-' . self::$data['project_version'];
			$name .= '_s-' . self::$data['project_status'];
			return 'livepack_' . md5($name) . self::$pack_extension;
		}
		else
		{
			$name = 'update_' . self::$data['project_tag'];
			$name .= '_v-' . self::$data['project_version'];
			$name .= '_s-' . self::$data['project_status'];
			$name .= '_u-' . $update;
			return 'pack_' . md5($name) . self::$pack_extension;
		}
	}
}
?>